
#include <ell/mempool.h>

#include <assert.h>
#include <stdio.h>
#include <string.h>

#ifndef ELL_MEMPOOL_DEFAULT_RESERVE
#define ELL_MEMPOOL_DEFAULT_RESERVE 0
#endif

#ifndef ELL_MEMPOOL_DEFAULT_CHUNK_SIZE
#define ELL_MEMPOOL_DEFAULT_CHUNK_SIZE 1024
#endif

#if ELL_MEMPOOL_DEFAULT_RESERVE < 0
#error "negative ELL_MEMPOOL_DEFAULT_RESERVE"
#endif

#if ELL_MEMPOOL_DEFAULT_CHUNK_SIZE <= 0
#error "non-positive ELL_MEMPOOL_DEFAULT_CHUNK_SIZE"
#endif

struct ell__mempool_chunk {
    struct ell__mempool_chunk* next_chunk;
    size_t chunk_size;
    void* data;
};

static void ell__mempool_add_chunk(struct ell_mempool* pool)
{
    struct ell__mempool_chunk* chunk = malloc(sizeof(struct ell__mempool_chunk));

    chunk->data = malloc(pool->element_size * pool->chunk_size);

    chunk->next_chunk = pool->chunks;
    chunk->chunk_size = pool->chunk_size;

    pool->capacity += pool->chunk_size;
    pool->chunks = chunk;

    pool->size += pool->chunk_size;

    for (unsigned int i = 0; i < pool->chunk_size; i++) {
        ell_mempool_ret(pool, chunk->data + i * pool->element_size);
    }
}

/* Interface implementation */

void ell_mempool_init(struct ell_mempool* pool, size_t element_size)
{
    ell_mempool_init3(
      pool, element_size, ELL_MEMPOOL_DEFAULT_RESERVE, ELL_MEMPOOL_DEFAULT_CHUNK_SIZE);
}
void ell_mempool_init2(struct ell_mempool* pool, size_t element_size, size_t reserve)
{
    ell_mempool_init3(pool, element_size, reserve, ELL_MEMPOOL_DEFAULT_CHUNK_SIZE);
}

void ell_mempool_init3(struct ell_mempool* pool,
  size_t element_size,
  size_t reserve,
  size_t chunk_size)
{
    if (pool == NULL) {
        return;
    }

    /*
     * Leaky implementation detail:
     * The freelist is composed of pointers stored in element locations,
     * therefore an element must at least be able to hold one pointer.
     */

    if (element_size < sizeof(void*)) {
        fprintf(stderr,
          "%s: illegal ELEMENT SIZE of %ld, at least size of %ld required\n",
          __func__,
          element_size,
          sizeof(void*));
        abort();
    }

    memset(pool, 0, sizeof(*pool));

    pool->element_size = element_size;
    pool->chunk_size = chunk_size;
    pool->num_chunks = 0;
    pool->capacity = 0;
    pool->size = 0;
    pool->chunks = NULL;
    pool->next_free = NULL;

    /* allocate initial chunks */

    ell_mempool_reserve(pool, reserve);
}

void ell_mempool_free(struct ell_mempool* pool)
{
    assert(pool != NULL);

    struct ell__mempool_chunk* chunk = pool->chunks;
    struct ell__mempool_chunk* tmp;

    while (chunk != NULL) {
        tmp = chunk->next_chunk;

        free(chunk->data);
        free(chunk);

        chunk = tmp;
    }
}

void ell_mempool_reserve(struct ell_mempool* pool, size_t reserve)
{
    assert(pool != NULL);

    if (pool->capacity >= reserve) {
        return;
    }

    size_t required = reserve - pool->capacity;

    size_t required_chunks = required / pool->chunk_size + (required % pool->chunk_size ? 1 : 0);

    for (size_t i = 0; i < required_chunks; i++) {
        ell__mempool_add_chunk(pool);
    }
}

/* Memory management */

void* ell_mempool_get(struct ell_mempool* pool)
{
    assert(pool != NULL);

    while (pool->capacity <= pool->size) {
        ell__mempool_add_chunk(pool);
    }

    void* ptr = pool->next_free;

    pool->next_free = *((void**)ptr);

    pool->size++;

    return ptr;
}

void ell_mempool_ret(struct ell_mempool* pool, void* ptr)
{
    assert(pool != NULL);

    *((void**)ptr) = pool->next_free;

    pool->next_free = ptr;

    pool->size--;
}

/* Queries */

size_t ell_mempool_size(const struct ell_mempool* pool)
{
    assert(pool != NULL);

    return pool->size;
}

size_t ell_mempool_capacity(const struct ell_mempool* pool)
{
    assert(pool != NULL);

    return pool->capacity;
}
